package controller_jcj

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
	"tools-api/src/common/def"
	"tools-api/src/common/utils"
	"tools-api/src/routes/request"
	"tools-api/src/routes/response"
)

// VerifySignature 校验签名
func VerifySignature(ctx *gin.Context) {
	// 1. 获取请求中的签名
	clientSign := ctx.GetHeader(request.JcjHeaderKeySignHeader)
	clientTimestamp := ctx.GetHeader(request.JcjHeaderKeyTimestampHeader)
	if clientSign == "" {
		clientSign = ctx.Query("sign") // 从URL参数获取
	}
	if clientTimestamp == "" {
		clientTimestamp = ctx.Query("timestamp") // 从URL参数获取
	}
	if clientSign == "" || len(clientTimestamp) != 10 {
		response.FailByCode(ctx, response.RequestForbidden, "参数错误")
		ctx.Abort()
		return
	}
	// 检验请求时间是否有效
	clientTimestamp2, _ := strconv.ParseInt(clientTimestamp, 10, 64)
	if clientTimestamp2 <= 0 {
		response.FailByCode(ctx, response.RequestForbidden, "参数错误")
		ctx.Abort()
		return
	}
	timeNow := time.Now().Unix()
	between := timeNow - clientTimestamp2
	timeout := def.Config.GetInt64("app.http.timeout") // 从配置获取
	if between > timeout || between < -5 {             // 请求超时 允许有5秒钟负的误差
		response.FailByCode(ctx, response.RequestForbidden, "请求失效")
		ctx.Abort()
		return
	}
	// 2. 收集所有请求参数
	params := make(map[string]string)
	params["timestamp"] = clientTimestamp

	// 获取URL参数
	for k, v := range ctx.Request.URL.Query() {
		if k != "sign" { // 排除签名参数本身
			params[k] = strings.Join(v, ",")
		}
	}

	// 获取表单参数
	if ctx.Request.Method == http.MethodPost || ctx.Request.Method == http.MethodPut {
		contentType := ctx.ContentType()
		switch contentType {
		case "application/x-www-form-urlencoded", "multipart/form-data":
			// 处理表单参数
			if err := ctx.Request.ParseForm(); err != nil {
				response.FailByCode(ctx, response.RequestForbidden, "参数解析错误")
				ctx.Abort()
				return
			}
			for k, v := range ctx.Request.PostForm {
				if k != "sign" {
					params[k] = strings.Join(v, ",")
				}
			}
		case "application/json":
			// 处理JSON参数
			var jsonData map[string]interface{}
			if err := ctx.ShouldBindBodyWith(&jsonData, binding.JSON); err == nil {
				for k, v := range jsonData {
					if k != "sign" {
						params[k] = fmt.Sprintf("%v", v)
					}
				}
			}
		}
	}

	// 3. 生成签名
	serverSign := generateSignature(params)

	// 4. 校验签名
	if serverSign != clientSign {
		response.FailByCode(ctx, response.RequestForbidden, "无效请求")
		ctx.Abort()
		return
	}

	ctx.Next()
}

// generateSignature 生成签名
func generateSignature(params map[string]string) string {
	// 1. 按key排序
	keys := make([]string, 0, len(params))
	for k := range params {
		keys = append(keys, k)
	}
	sort.Slice(keys, func(i, j int) bool { // 排序
		a := keys[i]
		b := keys[j]
		if len(a) < len(b) {
			return false
		} else if len(a) > len(b) {
			return true
		}
		return keys[i] < keys[j]
	})

	// 2. 拼接参数
	var paramStr strings.Builder
	for _, k := range keys {
		paramStr.WriteString(k)
		paramStr.WriteString("=")
		paramStr.WriteString(params[k])
		paramStr.WriteString("&")
	}

	// 3. 添加密钥
	signKey := def.Config.GetString("app.http.signKey")
	paramStr.WriteString("key=")
	paramStr.WriteString(signKey[16:])

	// 4. 计算SHA256
	return utils.HmacSha256(signKey, paramStr.String())
}
